/*
 * ------------------------------------------------------------------
 * Copyright © 2017, Hangzhou  DtDream Technology Co.,Ltd. All rights reserved.
 * ------------------------------------------------------------------
 * Product : DTWare
 * Module Name : MOC
 * Date Created: 2017-08-04
 * Description :
 * ------------------------------------------------------------------
 * Modification History * DATE Name Description
 * ------------------------------------------------------------------
 * 2017-08-04 z0228 refactor project
 * ------------------------------------------------------------------
 */
/*
 * Copyright 2012-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.gitee.booting.dspi.processor;

import com.gitee.booting.dspi.Dspi;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Annotation {@link Processor} that writes meta-data file for
 * {@code @Export}.
 * <p>
 * Author Stephane Nicoll
 * Author Phillip Webb
 * Author Kris De Volder
 *
 * @since 1.2.0
 */
@SupportedAnnotationTypes({Parser.DSPI})
public class DspiMetadataAnnotationProcessor extends AbstractProcessor {
    public static final String OBJECT = Object.class.getName();

    private final Map<String, Map<String, String>> meta = new HashMap<>();
    private MetadataStore metadataStore;
    private MetadataCollector metadataCollector;

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        this.metadataStore = new MetadataStore(env);
        this.metadataCollector = new MetadataCollector();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement te : annotations) {
            final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(te);
            for (Element element : elements) {
                if (element instanceof TypeElement) {
                    Dspi dspi = element.getAnnotation(Dspi.class);
                    for (TypeMirror type : ((TypeElement) element).getInterfaces()) {
                        if (type instanceof DeclaredType) {
                            processing(meta, ((TypeElement) element).getQualifiedName().toString(),
                                (TypeElement) ((DeclaredType) type).asElement(), dspi);
                        }
                    }
                }
            }
        }

        // Sync metadata to file.
        if (roundEnv.processingOver()) {
            meta.forEach((t, ni) -> metadataCollector.insert(t + "=\\\n" + ni.entrySet().stream()
                .map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(",\\\n"))));
            writeMetaData();
        }

        return false;
    }

    protected void processing(Map<String, Map<String, String>> meta, String impl, TypeElement element, Dspi dspi) {
        TypeMirror tm = element.getSuperclass();
        if (tm instanceof TypeElement && ((TypeElement) tm).getQualifiedName().contentEquals(OBJECT)) {
            processing(meta, impl, (TypeElement) tm, dspi);
        }
        String i = element.getQualifiedName().toString();
        if (!OBJECT.equals(i)) {
            meta.computeIfAbsent(i, x -> new HashMap<>());
            for (String name : dspi.names()) {
                meta.get(i).put(name, impl);
            }
            meta.get(i).put(impl.substring(impl.lastIndexOf(".") + 1).toLowerCase(), impl);
            for (TypeMirror type : ((TypeElement) element).getInterfaces()) {
                if (type instanceof DeclaredType) {
                    processing(meta, impl, (TypeElement) ((DeclaredType) type).asElement(), dspi);
                }
            }
        }
    }

    protected void writeMetaData() {
        List<String> metadatas = this.metadataCollector.getMetadatas();
        if (!metadatas.isEmpty()) {
            try {
                this.metadataStore.writeMetadatas(metadatas);
            } catch (IOException e) {
                logWarning(getStackTrace(e));
            }
        }
    }

    private String getStackTrace(Exception ex) {
        StringWriter writer = new StringWriter();
        ex.printStackTrace(new PrintWriter(writer, true));
        return writer.toString();
    }

    private void logWarning(String msg) {
        log(Kind.WARNING, msg);
    }

    private void log(Kind kind, String msg) {
        this.processingEnv.getMessager().printMessage(kind, msg);
    }
}
